<?php

namespace pmvc\mvc\annotation;

use pmvc\ioc\Ordered;

use pmvc\log\Log;

use pmvc\util\ReflectionUtil;
use pmvc\util\StringUtil;

use pmvc\annotation\AnnotationUtil;

use pmvc\ioc\ObjectDescriptor;
use pmvc\ioc\ObjectFactoryUtil;
use pmvc\ioc\ObjectFactory;
use pmvc\ioc\ObjectFactoryAware;

use pmvc\mvc\RequestFilterMapping;
use pmvc\mvc\MappedObject;
use pmvc\mvc\Request;

use ReflectionClass;
use ReflectionMethod;

/**
 * Implementation of the {@link RequestFilterMapping} interface
 * that uses annotations to map {@link RequestFilter}s to
 * {@link Request}s.
 */
class AnnotationsRequestFilterMapping
	implements RequestFilterMapping,
	ObjectFactoryAware {

	/**
	 * @var ObjectFactory
	 */
	private $objectFactory;

	/**
	 * {@inheritDoc}
	 */
	public function getRequestFilters($url, Request $request) {

		// get the descriptors
		$descriptors = ObjectFactoryUtil::getDescriptorsByClassAnnotations(
			$this->objectFactory, Array('pmvc\mvc\annotation\RequestFilter'));

		// our return array
		$ret = Array();

		// get the mappings
		foreach ($descriptors as $descriptor) {

			// get the class
			$clazz = new ReflectionClass($descriptor->class);

			// find controller methods
			$mappedObject = null;
			foreach ($clazz->getMethods() as $method) {

				// get mapped object
				$mappedObject = $this->getMappedObjectFromMethod(
					$method, $descriptor, $request, $url);
				if (isset($mappedObject)) {
					$ret []= $mappedObject;
				}
			}
		}

		// return them
		return $ret;
	}

	/**
	 * Inspects the {@link ReflectionMethod} for {@link RequestFilterMapping} annotations
	 * and returns a {@link MappedObject} for matches.
	 * @param ReflectionMethod $method
	 * @param ObjectDescriptor $desc
	 * @param Request $request
	 * @param string $url
	 * @return MappedObject
	 */
	private function getMappedObjectFromMethod(
		ReflectionMethod $method, ObjectDescriptor $desc, Request $request, $url) {
	
		// get the RequestMapping annotations
		$annotations = AnnotationUtil::getMethodAnnotations(
				$method, Array('pmvc\mvc\annotation\RequestFilterMapping'));
		if (empty($annotations)) {
			return null;
		}
	
		// check to see if it matches the request
		foreach ($annotations as $annot) {
	
			// first check request method
			if (isset($annot->method) && !empty($annot->method)) {
				if (!in_array(strtoupper($request->getMethod()), $annot->method)) {
					continue;
				}
			}
	
			// next check parameters
			if (isset($annot->params)) {
				$allPresent = true;
				foreach ($annot->params as $param) {
					if (!$request->hasParameter($param)) {
						$allPresent = false;
						break;
					}
				}
				if (!$allPresent) {
					continue;
				}
			}
	
			// now check the url
			$mappedObject = null;

			// get the order
			$order = isset($annot->order)
				? $annot->order
				: Ordered::LOWEST_PRECEDENCE;
	
			// try exact match
			if (isset($annot->url) && $annot->url==$url) {
				Log::trace(__CLASS__, "url: {0} matches request url: {1} for: {2}",
					$annot->url, $url, $desc->class);
				$obj = $this->objectFactory->getObjectById($desc->id);
				return new MappedObject(new AnnotatedRequestFilter($obj, $method), $annot->url, $order);
			}
				
			// try regex
			if (isset($annot->regex) && StringUtil::matches($annot->regex, $url, false)) {
				Log::trace(__CLASS__, "regex: {0} matches request url: {1} for: {2}",
					$annot->regex, $url, $desc->class);
				$obj = $this->objectFactory->getObjectById($desc->id);
				return new MappedObject(new AnnotatedRequestFilter($obj, $method), $annot->regex, $order);
			}
	
			// try token replacement
			if (isset($annot->url)) {
				$regex = "^".preg_replace("\1".'\{([^\/\{\}]+)\}'."\1", '(?<$1>[^\/\{\}]+)', $annot->url)."$";
				if (StringUtil::matches($regex, $url, false)) {
					Log::trace(__CLASS__, "url: {0} matches request url: {1} for: {2}",
						$annot->url, $url, $desc->class);
					$obj = $this->objectFactory->getObjectById($desc->id);
					return new MappedObject(new AnnotatedRequestFilter($obj, $method), $regex, $order);
				}
			}
				
		}
	
		// nothing found
		return null;
	}

	/**
	 * {@inheritDoc}
	 */
	public function setObjectFactory(ObjectFactory $objectFactory) {
		$this->objectFactory = $objectFactory;
	}

}

?>